﻿using System;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.IO;
using System.Collections.ObjectModel;
using System.ComponentModel;


namespace uploadTest1
{
	public partial class MFUpload : UserControl
	{
        private long chunkSize = 4194304;
        [Description("File upload Chunk size.(Default 4194304)")]        
        
        public long FileSizeLimit { get { return chunkSize; } set { chunkSize = value; } }

        private bool useGuidName = true;
        [Description("Use GUID for upload names? (Default true)")]
        public bool UseGuidName { get { return useGuidName; } set { useGuidName = value; } }

        [Description("Contains the GUID names for the uploaded files")]
        public Collection<String> GuidNames = new Collection<string>();

        private string savePath = "Uploads";
        [Description("Upload save path.(Default 'Uploads')")]
        public object UploadPath { get { return savePath; } set { savePath = value.ToString(); } }

        private bool multiselect=true;
        [Description("Enable multi upload.(Default true)")]
        public bool MultipleFiles { get { return multiselect; } set { multiselect = value; } }

        [Description("Total Number of files.")]
        public int numberOfFiles;
        [Description("Number of sucessfull sent files.")]
        public int numberOfFilesSent;

        private string filter = "All Files (*.*)|*.*";
        [Description("Desired file filter for OpenFileDialog(Default *.*)")]
        public string Filter { get { return filter; } set { filter = value.ToString(); } }

        [Description("The list of files selected")]
        public ObservableCollection<FileInfo> uploadedFileList = new ObservableCollection<FileInfo>();
        
        [Description("File Upload Complete")]
        public delegate void FileUploadedHandler (object sender, FileUploadedEventArgs e);

        [Description("All Files Sent")]
        public delegate void AllFilesUploadedHandler(object sender, FilesSentEventArgs e);

        [Description("Before sending")]
        public delegate void SendingFilesHandler(object sender, FilesSendingEventArgs e);

        [Description("Error")]
        public delegate void ErrorSendingHandler(object sender);

        [Description("File Upload Complete")]
        public event FileUploadedHandler FileUploaded;

        [Description("All Files Sent")]
        public event AllFilesUploadedHandler AllFilesUploaded;

        [Description("Before Sending Files")]
        public event SendingFilesHandler SendingFiles;

        [Description("Before Sending Files")]
        public event ErrorSendingHandler Error;

        private OpenFileDialog od = new OpenFileDialog();
        private FileInfo uploadedFile;
        private FileStream fileStream;        
        private long dataLength;
        private long dataSent;        
        private string docType;
        private string baseUri;       
        private ISynchronizationContext synContext;               
        private int hoverID = -1;
        
        /// <summary>
        /// Defines the Event Arguments relevent to file uploads
        /// long DataLength - The actuall data size of the file
        /// long DataSent - The amount of data sent to the server
        /// </summary>
        public class FileUploadedEventArgs : EventArgs
        {
            private long dataLength;
            private long dataSent;
            public FileUploadedEventArgs(long dataSent, long dataLength)
            {
                this.dataLength = dataLength;
                this.dataSent = dataSent;
            }
            public long DataLength
            {
                get { return this.dataLength; }
            }
            public long DataSent
            {
                get { return this.dataSent; }
            }
        }

        /// <summary>
        /// Defines the Event Arguments relevent to file uploads
        /// int numberOfFiles - The total number of files in the list box (not dialog)
        /// int numberOfFilesSent - The number of files sucessfully sent to the server
        /// </summary>
        public class FilesSentEventArgs : EventArgs
        {
            
            
            private int numberOfFiles;
            private int numberOfFilesSent;
            public FilesSentEventArgs(int numberOfFilesSent, int numberOfFiles)
            {
                this.numberOfFiles = numberOfFiles;
                this.numberOfFilesSent = numberOfFilesSent;
            }
            public long NumberOfFiles
            {
                get { return this.numberOfFiles; }
            }
            public long NumberOfFilesSent
            {
                get { return this.numberOfFilesSent; }
            }
        }

        public class FilesSendingEventArgs : EventArgs
        {
            private int numberOfFiles;            
            public FilesSendingEventArgs(int numberOfFiles)
            {
                this.numberOfFiles = numberOfFiles;                
            }
            public long NumberOfFiles
            {
                get { return this.numberOfFiles; }
            }
            
        }

		public MFUpload()
		{
			// Required to initialize variables
			InitializeComponent();
		}

		/// <summary>
		/// Reset the Forms visual components to pre-file selected state
		/// </summary>
		public void reset()
        {
            UploadButton.IsEnabled = false;
            clearBtn.IsEnabled = false;
            filesLst.IsEnabled = false;
            browseBtn.IsEnabled = true;            
            progressArea.Visibility = Visibility.Collapsed;         
        }

        /// <summary>
        /// The the results of the OpenFileDialog
        /// and append them to an ObservableCollection of FileInfo
        /// that is then bound as the ItemSource for the listBox display
        /// </summary>
        private void displayFileList()
        {
            filesLst.IsEnabled = true;
            foreach(FileInfo f in od.Files)
            {
                uploadedFileList.Add(f);
            }
            filesLst.ItemsSource = uploadedFileList;
        }

        /// <summary>
        /// Set the visual display to upload mode and then initilze file sending.
        /// SendingFiles event is triggered before any other action
        /// </summary>
        /// <param name="fileToUpload">A single FileInfo object to upload</param>
        private void uploadFile(FileInfo fileToUpload)
        {
            if (SendingFiles != null)
            {
                SendingFiles(this, new FilesSendingEventArgs(od.Files.Count()));
            }
            browseBtn.IsEnabled = false;
            clearBtn.IsEnabled = false;
            UploadButton.IsEnabled = false;
            filesLst.IsEnabled = false;                
            StartUpload(fileToUpload);          
        }

        /// <summary>
        /// Initilize the upload
        /// </summary>
        /// <param name="file">A single FileInfo object to upload</param>
        private void StartUpload(FileInfo file)
        {
            string filename = file.Name;
            progressArea.Visibility = Visibility.Visible;            
            fileStream = file.OpenRead();
            dataLength = fileStream.Length;
			UploadFileName.Text=file.Name;
            overallProgress.Value = 0;
            overallProgress.Maximum = dataLength;            
            long dataToSend = dataLength - dataSent;
            bool isLastChunk = dataToSend <= chunkSize;
            bool isFirstChunk = dataSent == 0;
           
            docType = file.Extension;
            UriBuilder httpHandlerUrlBuilder = new UriBuilder(String.Format("{0}/FileUpload.ashx", baseUri));
            if (useGuidName)
            {
                filename= Guid.NewGuid().ToString().Replace("-","")+docType;
                GuidNames.Add(filename);
            }
            httpHandlerUrlBuilder.Query = String.Format("{5}file={0}&offset={1}&last={2}&first={3}&docType={4}&savePath={6}", filename, dataSent, isLastChunk, isFirstChunk, docType, string.IsNullOrEmpty(httpHandlerUrlBuilder.Query) ? "" : httpHandlerUrlBuilder.Query.Remove(0, 1) + "&", savePath);

            HttpWebRequest webRequest =
     (HttpWebRequest)WebRequest.Create(httpHandlerUrlBuilder.Uri);
            webRequest.Method = "POST";
            webRequest.BeginGetRequestStream(new AsyncCallback(writeToStreamCallback), webRequest);
        }

        /// <summary>
        /// Write the file data to the server
        /// </summary>
        /// <param name="asynchronousResult">File write result</param>
        private void writeToStreamCallback(IAsyncResult asynchronousResult)
        {
            HttpWebRequest webRequest = (HttpWebRequest)asynchronousResult.AsyncState;
            Stream requestStream = webRequest.EndGetRequestStream(asynchronousResult);

            byte[] buffer = new Byte[4096];
            int bytesRead = 0;
            int tempTotal = 0;

            //Set the start position
            fileStream.Position = dataSent;

            //Read the next chunk
            while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length))
            != 0 && tempTotal + bytesRead < chunkSize)
            {
                requestStream.Write(buffer, 0, bytesRead);
                requestStream.Flush();

                dataSent += bytesRead;
                tempTotal += bytesRead;

                ////Show the progress change
                UpdateShowProgress(false);
            }

            requestStream.Close();

            //Get the response from the HttpHandler
            webRequest.BeginGetResponse(
            new AsyncCallback(ReadHttpResponseCallback), webRequest);
        }
      
        /// <summary>
        /// Write chunks to the server untill the entire file is sent.
        /// Upon completeion of all files allow AllFilesUploaded event to be raised.
        /// Upon error allow WriteError even to be raised
        /// </summary>
        /// <param name="asynchronousResult">File write result</param>
        private void ReadHttpResponseCallback(IAsyncResult asynchronousResult)
        {

           try
            {
                HttpWebRequest webRequest = 
                         (HttpWebRequest)asynchronousResult.AsyncState;
                HttpWebResponse webResponse = 
                         (HttpWebResponse)webRequest.EndGetResponse(asynchronousResult);
                StreamReader reader = new StreamReader(webResponse.GetResponseStream());

                string responsestring = reader.ReadToEnd();
                reader.Close();
            }
            catch
            {
                synContext.InvokeSynchronously(delegate()
                {
                    if (Error != null)
                    {
                        Error(this);
                    }
                });
            }

            if (dataSent < dataLength)
            {
                //continue uploading the rest of the file in chunks
                StartUpload(uploadedFile);

                //Show the progress change
                UpdateShowProgress(false);
            }
            else
            {
                fileStream.Close();
                fileStream.Dispose();
                
                //Show the progress change
                UpdateShowProgress(true);
                //check for more files to upload
                if (numberOfFilesSent < numberOfFiles)
                {
                    synContext.InvokeSynchronously(delegate()
                    {
                        uploadFile(uploadedFileList[numberOfFilesSent]);                        
                    });
                }
                else
                {
                    synContext.InvokeSynchronously(delegate()
                    {
                        if (AllFilesUploaded != null)
                        {
                            AllFilesUploaded(this, new FilesSentEventArgs(numberOfFiles, numberOfFilesSent));
                        }
                        uploadedFileList.Clear();
                        GuidNames = new Collection<string>();
                    });
                    
                }
            }

        }

        /// <summary>
        /// When a chunk is sent determin if that was the complete file, if so
        /// allow the FileUploaded event to be raised and then move to the next file
        /// </summary>
        /// <param name="complete">Did the send finish</param>
        private void UpdateShowProgress(bool complete)
        {
            if (complete)
            {
                synContext.InvokeSynchronously(delegate()
                {
                    if (FileUploaded != null)
                    {
                        FileUploaded(this, new FileUploadedEventArgs(dataSent, dataLength));
                    }
                    ++numberOfFilesSent;
                    //call the upload complete event                    
                    reset();                             
                });                
                dataSent = 0;
                dataLength = 0;
                fileStream = null;                
            }
            else 
            {
                synContext.InvokeSynchronously(delegate()
                {
                    overallProgress.Value = dataSent;                                        
                });                
            }
        }

        /// <summary>
        /// When the upload button is clicked prep the system to recieve uploads
        /// and start uploading the first file
        /// </summary>
        /// <param name="sender">sender</param>
        /// <param name="e">RoutedEventArgs</param>
        public void Upload_Click(object sender, RoutedEventArgs e)
        {            
            prepForFile();
            uploadFile(uploadedFileList[numberOfFilesSent]);            
        }

        /// <summary>
        /// Rested the system for file transfer (not the visual end)
        /// </summary>
        private void prepForFile()
        {
            synContext = UISynchronizationContext.Instance;
            Uri fullUri = Application.Current.Host.Source;
            baseUri = fullUri.AbsoluteUri.Substring(0, fullUri.AbsoluteUri.IndexOf("/ClientBin"));            
            uploadedFile = null;
            fileStream = null;
            dataLength = 0;
            dataSent = 0;
        }       

        /// <summary>
        /// User click on the browse button and a coresponding OpenFileDialog runs
        /// </summary>
        /// <param name="sender">sender</param>
        /// <param name="e">RoutedEventArgs</param>
        private void browseBtn_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            od.Filter = filter;
            od.Multiselect = multiselect;
            bool? gotfiles = od.ShowDialog();
            if ((bool)gotfiles)
            {
                numberOfFilesSent = 0;
                displayFileList();
                UploadButton.IsEnabled = true;
                clearBtn.IsEnabled = true;
                numberOfFiles = uploadedFileList.Count();
            }
        }

        /// <summary>
        /// When the user clicks on the clear button, reset the system
        /// </summary>
        /// <param name="sender">sender</param>
        /// <param name="e">RoutedEventArgs</param>
        private void clearBtn_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            reset();
            uploadedFileList.Clear();
        }

        /// <summary>
        /// Get the file ID of the list item that you are hovering on
        /// </summary>
        /// <param name="sender">sender</param>
        /// <param name="e">RoutedEventArgs</param>
        private void FileListItem_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
        {
            String fileHover = ((TextBlock)((Grid)sender).Children[2]).Text.ToString();
            for (int i = 0; i < uploadedFileList.Count; ++i)
            {
                if (uploadedFileList[i].Name == fileHover)
                {
                    hoverID = i;
                    break;
                }
            }          
        }

        /// <summary>
        /// Release the selected list ID when you leave move out of the box
        /// </summary>
        /// <param name="sender">sender</param>
        /// <param name="e">RoutedEventArgs</param>
        private void FileListItem_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
        {
            hoverID = -1;
        }

        /// <summary>
        /// Remove the selected file from the upload que, if all files are gone reset the uploader
        /// </summary>
        /// <param name="sender">sender</param>
        /// <param name="e">RoutedEventArgs</param>
        private void deleteBtn_Click(object sender, RoutedEventArgs e)
        {
            uploadedFileList.Remove(uploadedFileList[hoverID]);
            if (uploadedFileList.Count == 0)
            {
                reset();
            }
        }        
    }
}